package org.go.spi.impl;

import java.io.InputStream;
import java.net.URL;
import java.util.Iterator;
import java.util.LinkedList;

import org.go.spi.ClassLoadHelper;

/**
 * 
 * @author hejie
 *
 */
public class CascadingClassLoadHelper implements ClassLoadHelper {

	private ClassLoadHelper bestCandidate;

	private LinkedList<ClassLoadHelper> loadHelpers;

	/**
	 * Enable sharing of the "best" class-loader with 3rd party.
	 *
	 * @return the class-loader user be the helper.
	 */
	@Override
	public ClassLoader getClassLoader() {
		return (this.bestCandidate == null) ? Thread.currentThread().getContextClassLoader() : this.bestCandidate.getClassLoader();
	}

	/**
	 * Finds a resource with a given name. This method returns null if no
	 * resource with this name is found.
	 * @param name name of the desired resource
	 * @return a java.net.URL object
	 */
	@Override
	public URL getResource(String name) {

		URL result = null;

		if (bestCandidate != null) {
			result = bestCandidate.getResource(name);
			if (result == null)
				bestCandidate = null;
		}

		ClassLoadHelper loadHelper = null;

		Iterator iter = loadHelpers.iterator();
		while (iter.hasNext()) {
			loadHelper = (ClassLoadHelper) iter.next();

			result = loadHelper.getResource(name);
			if (result != null) {
				break;
			}
		}

		bestCandidate = loadHelper;
		return result;
	}

	/**
	 * Finds a resource with a given name. This method returns null if no
	 * resource with this name is found.
	 * @param name name of the desired resource
	 * @return a java.io.InputStream object
	 */
	@Override
	public InputStream getResourceAsStream(String name) {
		InputStream result = null;

		if (bestCandidate != null) {
			result = bestCandidate.getResourceAsStream(name);
			if (result == null) {
				bestCandidate = null;
			} else {
				return result;
			}
		}

		ClassLoadHelper loadHelper = null;

		Iterator iter = loadHelpers.iterator();
		while (iter.hasNext()) {
			loadHelper = (ClassLoadHelper) iter.next();

			result = loadHelper.getResourceAsStream(name);
			if (result != null) {
				break;
			}
		}

		bestCandidate = loadHelper;
		return result;
	}

	/**
	 * Called to give the ClassLoadHelper a chance to initialize itself,
	 * including the opportunity to "steal" the class loader off of the calling
	 * thread, which is the thread that is initializing Quartz.
	 */
	@Override
	public void initialize() {
		loadHelpers = new LinkedList<ClassLoadHelper>();
		loadHelpers.add(new LoadingLoaderClassLoadHelper());
		loadHelpers.add(new SimpleClassLoadHelper());
		loadHelpers.add(new ThreadContextClassLoadHelper());
		loadHelpers.add(new InitThreadContextClassLoadHelper());

		for (ClassLoadHelper loadHelper : loadHelpers) {
			loadHelper.initialize();
		}
	}

	/**
	 * Return the class with the given name.
	 */
	@Override
	public Class loadClass(String name) throws ClassNotFoundException {

		if (bestCandidate != null) {
			try {
				return bestCandidate.loadClass(name);
			} catch (Throwable t) {
				bestCandidate = null;
			}
		}

		Throwable throwable = null;
		Class clazz = null;
		ClassLoadHelper loadHelper = null;

		Iterator iter = loadHelpers.iterator();
		while (iter.hasNext()) {
			loadHelper = (ClassLoadHelper) iter.next();

			try {
				clazz = loadHelper.loadClass(name);
				break;
			} catch (Throwable t) {
				throwable = t;
			}
		}

		if (clazz == null) {
			if (throwable instanceof ClassNotFoundException) {
				throw (ClassNotFoundException) throwable;
			} else {
				throw new ClassNotFoundException(String.format("Unable to load class %s by any known loaders.", name), throwable);
			}
		}

		bestCandidate = loadHelper;

		return clazz;
	}

}
